Repository navigation
Remove dead code from the node:http2 legacy inbound path, the watcher loader column, unused native bindings, builtins.d.ts and misc crates - #39448
Conversation
…HPACK common-strings cache it fed
…lumn, and dead helpers in bun_core, bun_sys, bun_jsc, tcc_sys and builtins.d.ts
|
Status: ready for review (head 73bdc1a).
|
|
Caution CodeRabbit couldn't post its review summary. Error details |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (31)
💤 Files with no reviewable changes (17)
Included review availability: Your plan includes up to 5 reviews per rolling hour; 3 remain after this review. WalkthroughThe pull request removes obsolete public APIs, watcher loader plumbing, HTTP/2 common-string bindings, JavaScript intrinsic declarations, and related dead-symbol checks. It also updates source-lint workflow path filters. ChangesRuntime and source surface cleanup
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Review round addressed in 73bdc1a: the comment-cop finding on |
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main.
…ifiers cache (#39469) Follow-up to #39448, which deleted the dead `Http2CommonStrings` cache. This wires the live inbound path up to the cache that is actually in use. ### Problem - `Bun__h2__materializeHeaders` (`src/jsc/bindings/H2HeadersMaterializer.cpp`) runs once per inbound HEADERS / PUSH_PROMISE / trailer block on both the client and the server. For every field it allocated a new `JSString` cell for the name (`jsString(vm, String(httpHeaderNameStringImpl(...)))`), then read it back and called `Identifier::fromString` on it for the headers-object put, which is an atom-table lookup per field. - Pseudo-headers (`:method`, `:path`, `:scheme`, `:authority`, `:status`) are not in `HTTPHeaderNames.in`, so they took the unknown-name path: a new `StringImpl` copy plus a new `JSString` plus an atomization, on every request and every response. - `WebCore::HTTPHeaderIdentifiers` (`src/jsc/bindings/webcore/HTTPHeaderIdentifiers.{h,cpp}`, reached through `clientData(vm)->httpHeaderIdentifiers()`) already caches one `JSString` and one `Identifier` per known header name for the lifetime of the VM, and `Bun__NodeHTTP__buildRawHeadersArray` in `NodeHTTP.cpp` already uses it. The HTTP/2 path did not, and the cache had no pseudo-header entries. Its `identifierFor()` was removed as dead code in #39420 earlier today. ### Fix - `HTTPHeaderIdentifiers`: add `HTTP2_PSEUDO_HEADERS_EACH_NAME`, a second X-macro list with the five pseudo-headers, and feed it through the same declaration / lazy-init / accessor / visitor macros as the existing entries, so each pseudo-header gets the same `LazyProperty<JSString>` + cached `Identifier` pair. `HTTP2PseudoHeaderName` is generated from that list, so the enum and the lookup tables cannot drift apart; `findHTTP2PseudoHeaderName()` maps a wire name to it (one byte compare for names that do not start with `:`). - Restore `identifierFor(VM&, HTTPHeaderName)` next to `stringFor()` and add the `HTTP2PseudoHeaderName` overloads. The regular-name tables are indexed by `HTTPHeaderName`, so a `static_assert` now checks that `HTTP_HEADERS_EACH_NAME` has one entry per enum value and in enum order (previously nothing checked either; a misplaced entry would have silently shifted every later slot). - `H2HeadersMaterializer.cpp`: names found in either list take the cached `JSString` and `Identifier`, so they allocate nothing and are never atomized per request. Any other name is atomized straight from the wire bytes (the same atom-table lookup as before, minus the intermediate `StringImpl` copy) and its rawHeaders `JSString` wraps that atom, so the unknown path goes from two allocations to one. The `:status`, `cookie` and `set-cookie` special cases compare the enums instead of re-comparing the name bytes. Behavior of the produced rawHeaders array, headers object and sensitive list is unchanged. - `BunHttp2CommonStrings` is not brought back. - Verified: - `test/js/node/http2/node-http2.test.js`, `http2 hands out the cached name strings for pseudo-headers and known header names`: after one round trip, `jscDescribe()` from `bun:jsc` reports every name string in rawHeaders, on both the server and the client side, as atomic, which is only true of strings that wrap the atoms the headers object is keyed by (the cache entries, or the per-block atom for unknown names). On main all ten names come out non-atomic (a JSString over WebCore's static name string, or a copy of the wire bytes), so the test fails there and passes with this change. - Same file, `http2 materializes pseudo, known, unknown and all-digit header names the same way on every request`: pins the produced rawHeaders / headers object / sensitive list for all four name paths on both sides, including the cookie / set-cookie / `:status` special cases, and checks that a second round trip on the same session (served from the populated cache) materializes identically. Behavior-preservation test, passes before and after. - `bun bd test test/js/node/http2/` (all 9 files, including `h2-conformance.test.ts`): 477 pass, 0 fail, on the debug + ASAN build. - All 283 upstream `test/js/node/test/{parallel,sequential}/*http2*` files pass with the debug build. - The new test also passes with `BUN_JSC_validateExceptionChecks=1`. - The three touched C++ files are clang-format clean. ### Benchmark Local h2c loop in one process: a `node:http2` server and client on 127.0.0.1, 64 streams in flight on one session. Each round trip carries 10 regular fields on the request and 10 on the response (70% names from `HTTPHeaderNames.in`, 30% `x-*`), so with the pseudo-headers it materializes 25 fields per round trip (14 on the server, 11 on the client). `bun run build:release` (non-LTO) of main at 9f67488 with and without this diff, same machine (16 vCPU container on a shared, busy host), 7 interleaved trials each, 60k timed round trips per trial after a 5k warm-up. | headers per message | metric | before | after | delta | | --- | --- | ---: | ---: | ---: | | 10 (25 fields/round trip) | header fields/sec, median | 576,473 | 589,189 | +2.2% | | 10 | header fields/sec, best | 582,706 | 600,457 | +3.0% | | 10 | round trips/sec, median | 23,059 | 23,568 | +2.2% | | 50 (105 fields/round trip) | header fields/sec, median | 1,006,661 | 1,043,415 | +3.7% | | 50 | header fields/sec, best | 1,032,850 | 1,062,902 | +2.9% | | 50 | round trips/sec, median | 9,587 | 9,937 | +3.7% | The loop measures the whole request path (stream objects, events, framing, HPACK, loopback sockets), of which name materialization is a small slice, so a few percent end to end is the expected size of this change. Per round trip with the 10-header mix (5 pseudo-headers, 15 known names, 5 `x-*` names) it removes 20 `JSString` allocations, 10 `StringImpl` copies and 20 atom-table lookups; the 5 `x-*` names keep their lookup and go from two allocations to one. <details> <summary>Benchmark script</summary> ```js // usage: <bun> bench.mjs [regularHeadersPerMessage=10] [roundTrips=60000] [concurrency=64] import http2 from "node:http2"; const regular = Number(process.argv[2] ?? 10), total = Number(process.argv[3] ?? 60000), concurrency = Number(process.argv[4] ?? 64); const knownNames = ["content-type","cache-control","etag","vary","last-modified","accept","accept-encoding","user-agent", "authorization","accept-language","referer","origin","if-none-match","link","via"]; function buildHeaders(count, prefix) { const out = {}; let known = 0; for (let i = 0; i < count; i++) { if (i % 10 < 7 && known < knownNames.length) out[knownNames[known++]] = `${prefix}-value-${i}`; else out[`x-${prefix}-${i}`] = `${prefix}-value-${i}`; } return out; } const requestHeaders = { ":method": "POST", ":path": "/bench", ...buildHeaders(regular, "req") }; const responseHeaders = { ":status": 200, date: "Thu, 01 Jan 2026 00:00:00 GMT", ...buildHeaders(regular - 1, "res") }; const requestFields = 4 + regular, responseFields = 1 + regular; const server = http2.createServer(); let serverFields = 0; server.on("stream", stream => { serverFields += requestFields; stream.respond(responseHeaders, { endStream: true }); }); await new Promise(r => server.listen(0, "127.0.0.1", r)); const client = http2.connect(`http://127.0.0.1:${server.address().port}`); await new Promise(r => client.once("connect", r)); function run(n) { return new Promise((resolve, reject) => { let started = 0, finished = 0, clientFields = 0; function one() { started++; const req = client.request(requestHeaders, { endStream: true }); req.on("error", reject); req.on("response", h => { if (h[":status"] !== 200) reject(new Error("bad status")); clientFields += responseFields; }); req.on("close", () => { if (++finished === n) resolve(clientFields); else if (started < n) one(); }); } for (let i = 0; i < Math.min(concurrency, n); i++) one(); }); } await run(Math.min(total, 5000)); // warm-up serverFields = 0; const t0 = performance.now(); const clientFields = await run(total); const s = (performance.now() - t0) / 1000; console.log(JSON.stringify({ regular, roundTripsPerSec: Math.round(total / s), headerFieldsPerSec: Math.round((clientFields + serverFields) / s) })); client.close(); server.close(); ``` </details> ### Background - `HTTPHeaderNames.in` is WebCore's list of ~94 well-known HTTP header names. `findHTTPHeaderName()` maps a name to the `HTTPHeaderName` enum with a perfect hash. HTTP/2 pseudo-headers are not HTTP header fields, which is why they are not in that list. - `WTF::AtomStringImpl` is the per-thread interning table for strings used as property keys. A `JSC::Identifier` is a handle to an atom; `Identifier::fromString` hashes the input and looks it up (allocating a new atom on a miss). `JSString` is the GC cell that wraps a `StringImpl` and is what JavaScript sees; several `JSString`s can share one `StringImpl`, including an atom. - `JSC::LazyProperty<Owner, T>` is JSC's lazily-initialized GC slot: it stores an initializer until first use, and `visit()` marks the cell once created. `HTTPHeaderIdentifiers` lives in the per-VM `JSVMClientData` and is marked from `Zig::GlobalObject::visitChildren`, so the cached strings stay alive for the VM's lifetime and adding entries to the X-macro list extends both the initialization and the marking automatically. - Sharing one `JSString` cell across many rawHeaders arrays is not observable from JavaScript: strings are compared by value, and the cells are immutable. <!-- robobun:evidence:begin --> --- **[review]** gate passed · iteration 1 · 4 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 1 failed, 6 skipped $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/node/http2/node-http2.test.js" bun test v1.4.0 (8326d1b) test/js/node/http2/node-http2.test.js: (pass) node none > Client Basics > should be able to send a GET request [842.82ms] (pass) node none > Client Basics > should be able to send a POST request [564.28ms] (pass) node none > Client Basics > constants [18.74ms] (pass) node none > Client Basics > getDefaultSettings [7.14ms] (pass) node none > Client Basics > getPackedSettings/getUnpackedSettings [22.56ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is too small [5.48ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a multiple of 6 bytes [3.35ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a buffer [5.25ms] (pass) node none > Client Basics > should be able to send data using end [594.14ms] (pass) node none > Client Basics > should be able to mutiplex GET requests [582.90ms] (pass) node none > Client Basics > http2 should receive remoteSettings when receiving ... (truncated) release without fix: 6 skipped bun test v1.4.0-canary.1 (b4a5e9a) test/js/node/http2/node-http2.test.js: (pass) node none > Client Basics > constants [0.91ms] (pass) node none > Client Basics > getDefaultSettings [0.15ms] (pass) node none > Client Basics > getPackedSettings/getUnpackedSettings [0.39ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is too small [0.11ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a multiple of 6 bytes [0.04ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a buffer [0.07ms] (pass) node none > Client Basics > is possible to abort request [1.72ms] (pass) node none > Client Basics > aborted event should work with abortController [0.72ms] (pass) node none > Client Basics > aborted event should work with aborted signal [0.65ms] (pass) node none > Client Basics > signal validation matches node: non-signal objects throw, duck-typed { aborted } is accepted [1.09ms] (pass) node none > Client Basics > headers cannot be bigger than 65536 bytes [34.66ms] (skip) node none > Client Basics > should not leak memory (pass) node none > Client Basics > should fail to con ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: 6 skipped $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/node/http2/node-http2.test.js" bun test v1.4.0 (8326d1b) test/js/node/http2/node-http2.test.js: (pass) node none > Client Basics > should be able to send a GET request [860.23ms] (pass) node none > Client Basics > should be able to send a POST request [575.20ms] (pass) node none > Client Basics > constants [18.83ms] (pass) node none > Client Basics > getDefaultSettings [7.39ms] (pass) node none > Client Basics > getPackedSettings/getUnpackedSettings [23.12ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is too small [5.63ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a multiple of 6 bytes [3.68ms] (pass) node none > Client Basics > getUnpackedSettings should throw if buffer is not a buffer [5.08ms] (pass) node none > Client Basics > should be able to send data using end [607.15ms] (pass) node none > Client Basics > should be able to mutiplex GET requests [599.84ms] (pass) node none > Client Basics > http2 should receive remoteSettings when receiving ... (truncated) release with fix: 6 skipped $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 1049ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [1/126] gen generated_host_exports.rs generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 241 extern-C blocks audited [2/126] gen cpp.rs (cppbind) [3/126] gen JSSink.{cpp,h,lut.h,rs} generated_jssink.rs: 8 sinks, 96 exported symbols Generating /workspace/bun/build/release/codegen/JSSink.lut.h from /workspace/bun/build/release/codegen/JSSink.lut.txt [4/126] gen JS modules (bundle-modules) Preprocess modules (8180ms) Bundle modules (39ms) Postprocesss modules (36ms) Bundle Functions (629ms) Generate Code (22ms) [8.92s] Bundled "src/js" for production 2630 kb 197 internal modules 13 native modules 91 internal functions across 17 files [4/125] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu) nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19) �[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core) �[1m�[92m Compiling�[0m bun_highway v0.0.0 (/workspace/bun/src/highway) �[1m�[92m Compilin ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/jsc/bindings/H2HeadersMaterializer.cpp | 36 +++-- src/jsc/bindings/webcore/HTTPHeaderIdentifiers.cpp | 91 ++++++++++-- src/jsc/bindings/webcore/HTTPHeaderIdentifiers.h | 24 +++ test/js/node/http2/node-http2.test.js | 164 +++++++++++++++++++++ 4 files changed, 291 insertions(+), 24 deletions(-) ``` </details> **gate history** · 1 passed · 1 rejected · iteration 1 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/jsc/bindings/H2HeadersMaterializer.cpp 4 11 0 src/jsc/bindings/webcore/HTTPHeaderIdentifiers.cpp 5 13 0 src/jsc/bindings/webcore/HTTPHeaderIdentifiers.h 2 3 0 test/js/node/http2/node-http2.test.js 4 8 0 ``` </details> **root cause** · written by the author bot The inbound node:http2 header path in H2HeadersMaterializer.cpp built a fresh JSString and then a separate Identifier for every header name on every HEADERS frame, and the pseudo-headers were absent from HTTPHeaderNames.in, so each request or response also paid for a new StringImpl plus atomization for :method, :path, :scheme, :authority and :status. The fix routes known names through the existing per-VM HTTPHeaderIdentifiers cache, as NodeHTTP.cpp already does, and adds the five pseudo-headers to that cache as lazily initialized strings with cached identifiers, so steady-state materializat… <!-- robobun:evidence:end -->
…rs to 4 (#39466) ### Problem - `test/internal/source-lints/jsresult-swallow.test.ts` fails on main: `inventory shrinks when a swallow is fixed` reports `src/runtime/api/bun/h2_frame_parser.rs: "discarded result of a call that enters script" is 4 now, inventory says 5`. - The source-lints workflow on main has been red with exactly this one failure since 9f67488 (#39448); the run for the commit before it (8d9cbbd) was green. - Cause: `jsresult-swallow.inventory.json` landed in #38660 counting 5 sites in `h2_frame_parser.rs`. #39448 was opened before that, so its CI never saw the ratchet, and it deleted one of the 5 (the `let _ = self.handlers.get().call_event_handler(` in the legacy inbound path) without lowering the count. The file now has 4 (`h2_frame_parser.rs:2487`, `:2511`, `:2529`, `:2554`). ### Fix - Lowers the `h2_frame_parser.rs` entry from 5 to 4. The file was regenerated with the command the test header documents (`bun test/internal/source-lints/jsresult-swallow.test.ts --update`); this line is the only thing it changed, so every other entry still matches the tree. - Correct because a ratchet entry is a count of today's matches, and the test exists to force the count down when a match goes away; 4 is the number of matches left. - The remaining 4 are left alone on purpose. `call_event_handler` and `call_write_callback` (`h2_frame_parser.rs:762-795`) return a `bool` ("was the callback dispatched") and run the callback through `EventLoop::run_callback` (`src/jsc/event_loop.rs:409`), which already folds an `Err` with `report_error_or_terminate`, so those `let _ =` lines discard a `bool`, not a `JsResult`; the lint counts them because its pattern names those helpers (`jsresult-swallow.test.ts:32`). Whether to retire them by narrowing the pattern or reshaping the helpers is a change to the ratchet itself, separate from unbreaking main. - Verified: `bun test test/internal/source-lints/jsresult-swallow.test.ts` fails on main (7aad387) with the message above and passes with this change. `bun test test/internal/source-lints/` is green with it (183 pass across 27 files; these lints read the source tree and do not run the built binary, per the directory README). ### Background - `test/internal/source-lints/` holds lints that grep `src/**`; they run in the `source-lints` GitHub workflow against a released bun, not against the build under test. - `jsresult-swallow.test.ts` is a ratchet: it greps Rust sources for patterns that discard a `JsResult` (a pending JS exception or a worker termination) and compares per-file counts with `jsresult-swallow.inventory.json`. A count above the inventory fails (`no new swallowed JsResult`); a count below it also fails (`inventory shrinks when a swallow is fixed`), so the inventory can only move down and must be lowered in the same change that removes a site. <!-- robobun:evidence:begin --> --- **no test proof** · iteration 0 · docs-only change; test-proof not applicable <!-- robobun:evidence:end -->
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from #39558 and the other-thread possibility the Send bounds rely on.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps #39547's shape (type check and dial/rejection before any listener is stored, no trailing else) with the rejection and the new check spelled through the scope.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with #39732's parameterless ReadableStream::done().
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take #39922's bodies (callback handed to the native job, one from_js call) under the scoped signatures.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is #39924's three thin host fns plus its run() helper, with the thin fns scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with #40051's comment sweep; verifySync keeps the deferred materialize and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing from Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws).
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls).
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9).
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry).
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry). Twenty-fourth rebase (9 more commits, onto 82123d3): six files, all with #40478 (RefPtr releases on Drop). This PR's StoreRef::adopt is retired: main's RefPtr<Store> is the same owning handle, so webcore_types.rs is main's again and store_backed_buffer_to_js moves a RefPtr<Store> into the JS object as the *_from_owner owner (the view closure reaches the bytes through Store::data_mut). The sql event-loop guard keeps this PR's safe EventLoop::scope under main's renamed ref guard; expect.rs keeps this PR's wrapping over main's RefPtr comments. The vm-thread-door inventory follows main's StoreRef-to-Store rename.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry). Twenty-fourth rebase (9 more commits, onto 82123d3): six files, all with #40478 (RefPtr releases on Drop). This PR's StoreRef::adopt is retired: main's RefPtr<Store> is the same owning handle, so webcore_types.rs is main's again and store_backed_buffer_to_js moves a RefPtr<Store> into the JS object as the *_from_owner owner (the view closure reaches the bytes through Store::data_mut). The sql event-loop guard keeps this PR's safe EventLoop::scope under main's renamed ref guard; expect.rs keeps this PR's wrapping over main's RefPtr comments. The vm-thread-door inventory follows main's StoreRef-to-Store rename. Twenty-fifth rebase (23 more commits, onto 0e395c2): four files, all with #40511 (async fs calls no longer pin Buffer paths). pbkdf2 and scrypt take main's from_js_async parsers (ThreadIsolated params) under the scoped signatures, StringOrBuffer keeps main's from_js_async next to this PR's from_js_scoped / from_js_deferred, and the BlobOrStringOrBuffer::from_js_async this PR's insertion sat beside is gone with main. Import merges in node.rs and MarkdownObject.rs. The vm-thread-door inventory follows main's ThreadSafe-to-ThreadIsolated rename.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry). Twenty-fourth rebase (9 more commits, onto 82123d3): six files, all with #40478 (RefPtr releases on Drop). This PR's StoreRef::adopt is retired: main's RefPtr<Store> is the same owning handle, so webcore_types.rs is main's again and store_backed_buffer_to_js moves a RefPtr<Store> into the JS object as the *_from_owner owner (the view closure reaches the bytes through Store::data_mut). The sql event-loop guard keeps this PR's safe EventLoop::scope under main's renamed ref guard; expect.rs keeps this PR's wrapping over main's RefPtr comments. The vm-thread-door inventory follows main's StoreRef-to-Store rename. Twenty-fifth rebase (23 more commits, onto 0e395c2): four files, all with #40511 (async fs calls no longer pin Buffer paths). pbkdf2 and scrypt take main's from_js_async parsers (ThreadIsolated params) under the scoped signatures, StringOrBuffer keeps main's from_js_async next to this PR's from_js_scoped / from_js_deferred, and the BlobOrStringOrBuffer::from_js_async this PR's insertion sat beside is gone with main. Import merges in node.rs and MarkdownObject.rs. The vm-thread-door inventory follows main's ThreadSafe-to-ThreadIsolated rename. Twenty-sixth rebase (8 more commits, onto 72ffcd8): one import-line conflict in ffi_body.rs, where #40592 added ErrorCode next to this PR's scoped imports. Both kept; no inventory changes.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry). Twenty-fourth rebase (9 more commits, onto 82123d3): six files, all with #40478 (RefPtr releases on Drop). This PR's StoreRef::adopt is retired: main's RefPtr<Store> is the same owning handle, so webcore_types.rs is main's again and store_backed_buffer_to_js moves a RefPtr<Store> into the JS object as the *_from_owner owner (the view closure reaches the bytes through Store::data_mut). The sql event-loop guard keeps this PR's safe EventLoop::scope under main's renamed ref guard; expect.rs keeps this PR's wrapping over main's RefPtr comments. The vm-thread-door inventory follows main's StoreRef-to-Store rename. Twenty-fifth rebase (23 more commits, onto 0e395c2): four files, all with #40511 (async fs calls no longer pin Buffer paths). pbkdf2 and scrypt take main's from_js_async parsers (ThreadIsolated params) under the scoped signatures, StringOrBuffer keeps main's from_js_async next to this PR's from_js_scoped / from_js_deferred, and the BlobOrStringOrBuffer::from_js_async this PR's insertion sat beside is gone with main. Import merges in node.rs and MarkdownObject.rs. The vm-thread-door inventory follows main's ThreadSafe-to-ThreadIsolated rename. Twenty-sixth rebase (8 more commits, onto 72ffcd8): one import-line conflict in ffi_body.rs, where #40592 added ErrorCode next to this PR's scoped imports. Both kept; no inventory changes. Twenty-seventh rebase (24 more commits, onto 49ff888): five files, all with #40516 (refcounted types own their teardown). The serve-plugins .then callbacks adopt their ref through main's RefPtr::from_raw under the scoped argument spellings (this PR's ServePluginsRef guard is gone with main's newtypes), FileSink keeps this PR's with_mut spelling over main's RefPtr<FileSink> construction (create is main's one-liner), the StatWatcher deinit hook stays deleted next to the scoped do_ref, and ipc_host.rs / socket_body.rs are import and return-spelling merges. No inventory changes.
…rship contracts compile errors Rebased onto main as a single commit; the branch history (with its merge commits) is not preserved. Adds a zero-cost branded scope layer over the raw JSC FFI (`src/jsc/scope.rs`: `Scope<'s>`, `Local<'s>`) that makes two boundary bug classes compile errors for code that stays on the scoped API: - a JS value escaping its host call unrooted (persisting requires the explicit `Scope::persist` -> `Strong`); - a JS-heap view (`Local::array_buffer_bytes`) held across an operation that can re-enter user JS (`&mut Scope`), e.g. a coercion that detaches the buffer. Codegen integration: - `#[bun_jsc::host_fn(scoped)]`: functions written as `fn(scope: &mut Scope, callframe) -> JsResult<Local>` get a macro-synthesized wrapper under their original name and unscoped signature, so js2native / `.classes.ts` / direct-call wiring stays byte-compatible. User `cfg`/doc/lint attributes propagate to the public wrapper and the extern shims. - `ZIG_EXPORT(tag, reenters_js | no_user_js)` effect markers on the C++ declarations; explicitly classified functions get branded wrappers generated into `bun_jsc::cpp::scoped` (`&mut Scope` / `&Scope`), unclassified and `null_is_throw` functions get none. All classified exports are verified against their C++ (`toMatch` and `putMayBeIndex` are `reenters_js`: a non-uint32 `lastIndex` goes through ToNumber, and index puts on exotic receivers reach `defineOwnProperty` traps). Migration: ~470 host functions are converted to the scoped form (behavior-preserving); the remaining escape hatches (`unscoped_global()` / `unscoped_bun_vm()` / `.unscoped()` and unscoped `#[host_fn]`s) are pinned per file by `test/internal/source-lints/scope-escapes.test.ts`. Also expresses the `make_*_with_bytes_no_copy` "pointer stays valid until the deallocator runs" contracts as ownership transfer (`typed_array_from_owned_slice` / `_from_vec` / `_from_owner`, `ForeignBytes`, `external_string_from_utf16`, `OwnedUrl`, `OwnedTextCodec`, `StoreRef::adopt`, `bun_sys::Mmap`, `EventLoop::scope`), replacing hand-paired create/destroy and leak-and-remember-to-free code paths. The static `Bun.CryptoHasher.hash` / `Bun.password.verifySync` argument detach bugs that motivated the layer were fixed independently on main (#36165) by coercing every argument first; here the same behavior is expressed through deferred `materialize` under the shared scope borrow, so reordering the view capture before a coercion is a borrow error. Both main's regression tests and the layer's suites pass. Rebase onto main (461 commits): 40 files conflicted; resolved by taking main's text and re-applying only the scope transformation. Changes that main made obsolete were dropped (TextEncoderStreamEncoder host fns, EventLoop::with_pipe_read_buffer, JSC__JSMap__size global arg, the sendHelperChild scoping). Follow-ups main's newer code required: JSValue::create_buffer_from_foreign now returns JsResult (the binding became fallible on main), ArrayBufferSink::end_from_js uses or_pending_exception (empty-jsvalue-laundering lint), TextDecoder createForStream uses struct update syntax (clippy, since the PR removes TextDecoder's Drop impl), ForeignBytes recorded in the vm-thread-door inventory, scope-escape limits regenerated, and the ratchet's regeneration mode is gated on an explicit --update flag. Second rebase (30 more commits, onto 8bc4d2a): two conflicts from the zero-fill removal (#39417). The zstd sync functions keep main's Failure enum and create_buffer_from_box behind the scoped signatures, and the latin1 TextDecoder path keeps main's uninitialized Vec but hands it to JSC through external_string_from_utf16_vec instead of the raw to_external_u16, matching the file's other two decode paths. Third rebase (6 more commits, onto 0002bf8): conflicts were all with the dead-code sweeps (#39420, #39448). Dropped the scoping of things main deleted (Bun.nanoseconds' host fn, ArrayBufferSink::to_js, the unreachable csrf error arm, three unused node:: re-exports) and kept the ownership refactor of ArrayBufferSink::end_from_js. Inventories regenerated; the jsresult-swallow one also picks up a count #39448 left stale on main. Fourth rebase (17 more commits, onto 258517a): do_publish keeps #39389's shape (topic JSString held and ensure_still_alive'd across the message conversion) under the scoped signature, and the valkey publish scoping sits after the command block #29339 added. Fifth rebase (42 more commits, onto 6948a12): OwnedTextCodec is gone with the WebKit codecs (#39485); TextDecoder keeps main's encoding_rs path and this PR's external_string_from_utf16_vec hand-offs, including on the new path. ArrayBuffer::from_owned_bytes stays deleted (no callers; its u32 cast that #39558 fixed never existed in the replacements), and the no-copy deallocator contract now states both the Err-path timing (the deallocator can run before Err, per #39558) and the cross-thread timing this PR's Send bounds rely on. Sixth rebase (18 more commits, onto 681a49b): three dead-code conflicts. The scoped js_assert_settings goes away with the native assertSettings (#38900, node:http2 validates in JS now), ParseArgumentsCfg's unused Default impl stays removed (#39585), and TimeoutObject keeps main's generated cached-accessor import next to the scoped imports. Seventh rebase (14 more commits, onto 32e8703): valkey subscribe() keeps the structure #39547 gave it (channel type check first, dial plus send_rejection() before a listener is stored, no trailing else) with the rejection and the new check spelled through the scope. Eighth rebase (36 more commits, onto 56c4e3d): the three expect matcher utils take #36912's propagating print_value; the conflict was only the line wrapping. memory_pressure.rs (new on main) is added to the scope-escape limits. Ninth rebase (17 more commits, onto 72ec6e2, which includes the #39839 build fix): FileSink::on_close combines this PR's with_mut probe with the parameterless ReadableStream::done() and is_some() guard from #39732. Tenth rebase (5 more commits, onto 4448a2e): the Windows cluster handle path keeps #39804's `?` on attach_windows_socket_payload under the scoped argument spelling. Eleventh rebase (7 more commits, onto a21f02a): pbkdf2/pbkdf2Sync take the bodies #39922 gave them (from_js also returns the callback, pbkdf2 returns undefined, length 6) under the scoped signatures. Twelfth rebase (16 more commits, onto 6fb7102): the safe vm_loop_ctx is re-applied onto #40002's Cell-based upgrade client, including inside the new clear_data's with_mut. Thirteenth rebase (15 more commits, onto d0f6486): the assert binding is scoped (5 hatches now, was 1); #39995's named-pipe live-count testing fn is scoped like its neighbours. Fourteenth rebase (12 more commits, onto e8300da): cron_remove's tail takes #40024's safe ThisPtr start_linux call under the scoped return; the rest of cron.rs's Cell/ThisPtr rewrite auto-merged. node_crypto_binding's scope-escape limit rises by the two unscoped argon2 host fns #37015 added. Fifteenth rebase (6 more commits, onto 1423031): two conflicts with the defer-comment sweep (#40051): PasswordObject's verifySync keeps this PR's deferred materialize of both arguments, and NodeHTTPResponse's on_resolve keeps the scoped call, both without the removed defer comments. Sixteenth rebase (31 more commits, onto 01008f8): the socket ref()/unref() host fns take #39856's bodies (hold the loop while connecting, apply the recorded state on open) under the scoped signatures. Seventeenth rebase (8 more commits, onto 4bb20e5): #40055 made the websocket upgrade client's loop-context plumbing safe itself (and dropped the adapter), so this PR's vm_loop_ctx change there is retired and both http_jsc files are main's. Eighteenth rebase (8 more commits, onto 3e347b3): the rootError crash hook keeps #37181's one-argument handle_root_error under the scoped signature. Nineteenth rebase (6 more commits, onto 7a5d837): #40251 removed the exception checks that follow already-checked calls and made JSString::to_slice / view, JSValue::get_zig_string and handle_ipc_message return JsResult. Eight files conflicted inside scoped bodies (BunObject, CryptoHasher, PasswordObject, ipc_host, node_util_binding, server_body, expect, ObjectURLRegistry); main's control flow is kept (the guards go, the ? is added) under the scoped spellings. The four has_exception checks left in BunObject.rs are the ones main kept (print_table / format2 swallow nested throws). Twentieth rebase (9 more commits, onto f2fe7d3): 32 files conflicted, nearly all with #40238 (bun_core::String owns its WTF ref). Main's ownership idioms replace this PR's: OwnedString / scopeguard deref wrappers and manual .deref() calls go (String drops its ref), into_js replaces transfer_to_js (Scope::transfer_string now consumes the String), JSValue::get_zig_string is gone so Local::get_zig_string becomes Local::to_js_string_view (the JSStringView guard keeps the cell alive), and to_slice_or_null collapses into to_slice. OwnedUrl is retired: main's whatwg::Parsed is the same RAII handle, so src/jsc/URL.rs and js_valkey.rs are main's again. ScopeFunctions.rs is rebuilt from main's text (the strings module is gone, names are &'static str) with the 14 host fns scoped and rustfmt applied; jest.rs and expect.rs take main's literals under this PR's wrapping. CachedStructure keeps main's assume_init_mut / drop_in_place sequence over this PR's slice-taking create_structure. UDP address getters add the ? main's create_sock_addr now needs. Scope-escape limits drop by one in BunObject, node_util_binding and server_body and by two in FormData (hatches replaced by scoped calls). Twenty-first rebase (7 more commits, onto 8335017): one import-line conflict in node_fs_binding.rs, where #38383 added the SystemErrorJsc trait import next to this PR's scoped imports. Both kept; no inventory changes. Twenty-second rebase (6 more commits, onto 0823e50): 39 files conflicted, all with #40374 (Utf8Bytes<'a> / EncodedSlice<'a>). Main's types replace the PR's spellings inside scoped bodies: Local::to_slice is now Local::to_utf8 (Utf8Bytes<'static>), ZigString::init(..).to_js and create_utf8_for_js calls become scope.string_utf8 / scope.string, and ScopedStringOrBuffer names StringOrBuffer<'static>. Main's owned_utf16_into_js supersedes this PR's external_string_from_utf16*, so src/jsc/ZigString.rs stays deleted and bun_string_jsc.rs and TextDecoder.rs are main's again. Scope-escape limits drop in filesystem_router (13 to 7), server_body (17 to 15) and Listener (11 to 9). Twenty-third rebase (9 more commits, onto adc354d): two files. FileSystemRouter::routes takes #40410's fallible JSValue::from_entries (mapped into the scope), and advanceTimersByTime keeps #40414's NaN check and main's message text under the scoped throws. The jsresult-swallow inventory is main's again (#40410 fixed the FakeTimers entry). Twenty-fourth rebase (9 more commits, onto 82123d3): six files, all with #40478 (RefPtr releases on Drop). This PR's StoreRef::adopt is retired: main's RefPtr<Store> is the same owning handle, so webcore_types.rs is main's again and store_backed_buffer_to_js moves a RefPtr<Store> into the JS object as the *_from_owner owner (the view closure reaches the bytes through Store::data_mut). The sql event-loop guard keeps this PR's safe EventLoop::scope under main's renamed ref guard; expect.rs keeps this PR's wrapping over main's RefPtr comments. The vm-thread-door inventory follows main's StoreRef-to-Store rename. Twenty-fifth rebase (23 more commits, onto 0e395c2): four files, all with #40511 (async fs calls no longer pin Buffer paths). pbkdf2 and scrypt take main's from_js_async parsers (ThreadIsolated params) under the scoped signatures, StringOrBuffer keeps main's from_js_async next to this PR's from_js_scoped / from_js_deferred, and the BlobOrStringOrBuffer::from_js_async this PR's insertion sat beside is gone with main. Import merges in node.rs and MarkdownObject.rs. The vm-thread-door inventory follows main's ThreadSafe-to-ThreadIsolated rename. Twenty-sixth rebase (8 more commits, onto 72ffcd8): one import-line conflict in ffi_body.rs, where #40592 added ErrorCode next to this PR's scoped imports. Both kept; no inventory changes. Twenty-seventh rebase (24 more commits, onto 49ff888): five files, all with #40516 (refcounted types own their teardown). The serve-plugins .then callbacks adopt their ref through main's RefPtr::from_raw under the scoped argument spellings (this PR's ServePluginsRef guard is gone with main's newtypes), FileSink keeps this PR's with_mut spelling over main's RefPtr<FileSink> construction (create is main's one-liner), the StatWatcher deinit hook stays deleted next to the scoped do_ref, and ipc_host.rs / socket_body.rs are import and return-spelling merges. No inventory changes. Twenty-eighth rebase (36 more commits, onto 69c6138): one import-line conflict in csrf_jsc.rs, where #40697 added IntegerRange next to this PR's scoped imports. Both kept; no inventory changes.
Net -2,649 lines under
src/(29 files, +28 / -2,677 bygit diff --minimal), plus a 249-line source lint pinning the removals. No behavior change: everything removed had no caller, and for the Rust parts rustc is the reference check (see Verification).Nothing here overlaps the 20 dead-code PRs currently open; their diffs were compared line by line, and files they edit near these hunks were left alone (see the last section).
node:http2: the legacy inbound frame path (-2,148 in
h2_frame_parser.rs, -154 in C++)Since the engine rewrite (#31584) every inbound byte goes through
rewrite_read()intoapi/h2/connection.rs; the pre-engine inbound half ofh2_frame_parser.rshas been unreachable since, and the file carried a file-level#![allow(dead_code)]with a comment saying so. This PR dropsdead_codefrom that attribute and deletes exactly what rustc then reports (the list below is rustc's, not grep's), so the file is now under the workspace'sdead_code = denylike every other file and the retired path cannot grow back.read_bytes,dispatch_frame,lookup_inbound_stream.handle_incomming_payload,handle_data_frame,handle_headers_frame,handle_continuation_frame,handle_settings_frame,handle_window_update_frame,handle_rst_stream_frame,handle_ping_frame,handle_go_away_frame,handle_priority_frame,handle_push_promise_frame,handle_altsvc_frame,handle_origin_frame,handle_unknown_frame) plusdecode_header_block,finish_headers_end_stream, and thePayloadstruct they returned.H2FrameParser::{decode, adjust_window_size, increment_window_size_if_needed, send_settings_ack, dispatch_with_3_extra},get_http2_common_stringand its extern declaration.u32_from_bytes,SettingsFlags,UInt31WithReserved::{from, from_bytes},StreamPriority::from,SettingsPayloadUnit(and itsfrom),FullSettingsPayload::{update_with, write},type HeaderValue,ErrorCode::FLOW_CONTROL_ERROR.H2FrameParser::{current_frame, remaining_length, expecting_continuation, preface_received_len, read_buffer}(the last one was only ever reset; the engine keeps its ownrewrite_tail) andStream::{is_waiting_more_headers, header_block_size, header_block_count, pending_header_block, pending_header_flags, padding}.FullSettingsPayload::writewas the only thing that used the struct's on-wire shape (the live serializer,write_settings_payload, writes ids as literals and reads only the value fields), so the seven_*_typefields,#[repr(C, packed)], the bytemuck impls,BYTE_SIZEand its size assert, and theSettingsTypeconstants that only initialized those fields go with it.to_engine_settings,to_jsanddefault()are unchanged; the two struct literals lose a..Default::default()that no longer fills anything.JSC__JSGlobalObject__getHTTP2CommonString(ZigGlobalObject.cpp) lost its only caller above, andBun::Http2CommonStrings(BunHttp2CommonStrings.h/.cpp: 61 lazily created JSStrings for the HPACK static table, one slot per global object) had no reader other than that function, so both files, them_http2CommonStringsmember and itsinitialize()call are removed. The entry for the deleted header indead-symbols-ffi-sys-test-runner-cpp.test.tsis dropped with it.Native class methods no built-in JS calls (-72)
Each was declared in a
.classes.tsfile and implemented in Rust, but nothing insrc/js/(ortest/) invokes it; none of these classes is reachable by user code (they live behind private fields of the JS wrappers). The.classes.tsentry and the implementation are removed together, so the regenerated bindings simply stop referencing them.QuicSession.silentClose(quic.classes.ts,session.rs) andQuicEndpoint.ref(fn: "doRef",endpoint.rs; the endpoint's keepalive is driven natively,poll_refstays).NodeJSFS.unwatchFile(node.classes.ts,node_fs_binding.rs,node_fs.rs): aMaybe::todo()stub;fs.unwatchFileis implemented ininternal/fs/watchfile.tsand never called the binding. Theargs::UnwatchFile/ret::UnwatchFilealiases only it used go too. It was also the only stub that calledMaybe::todo()on every target; the three remaining stubs (Windowslchmod, theFICLONE_FORCEarm ofcopy_fileand its fallback arm) now spellErr(sys::Error::todo()), which is all theMaybeTodoextension trait innode.rsdid, so the trait is removed as well.FSWatcher.hasRef(node.classes.ts,node_fs_watcher.rs);internal/fs/watch.tsonly callsref/unref/close. ThehasRefofTimeoutandImmediateis public API and untouched.The watcher's write-only
loadercolumn (-65 across 8 files)WatchItem.loaderwas stored for every watched file and directory and never read (noWatchItemColumnsaccessor, noitems::<"loader">anywhere). Removing the field removes thebun_watcher::Loadernewtype inwatcher/lib.rs, which existed only to carry it across the crate boundary, the drift-guardconst _assert inhot_reloader.rsthat kept itsFileconstant in sync withbun_ast::Loader, and theloaderparameter ofWatcher::{add_file, add_file_by_path_slow}and the private add helpers,ImportWatcher::{add_file, add_file_by_path_slow}andjsc_hooks::maybe_watch_file, along with the argument at every call site (bundle_v2.rsx2,RuntimeTranspilerStore.rsx2,jsc_hooks.rsx4,VirtualMachine.rs,test_command.rs;add_main_to_watcher_if_neededand thebun test --changedseeding loop no longer compute a loader they only passed along).pubhelpers with no caller in any crate (-65)bun_core:Unaligned::new,util::Mutex::get_mut,util::RwLock::get_mut(and the mention in the module note),Timespec::new,RawSlice::from_raw.bun_sys:DynLib::handle(the onlyDynLibuser,bun:ffi, never reads the raw handle) andposix::read(nothing importsbun_sys::posix::read; the live raw reader isbun_sys::linux::read), with the two comments that listed it.bun_jsc:JSValue::cast.tcc_sys: theSymbolCallbacktype alias and its re-export.src/js/builtins.d.ts(-131)Declarations of
$-names no file undersrc/js/references (checked as exact tokens acrosssrc/,src/codegen/,scripts/andtest/): 31 JSC intrinsic functions that were either(): TODOstubs or unused ($getByValWithThis,$getPrototypeOf,$fulfillPromise, the generator / iterator / module-record internal-field accessors,$isConstructor,$isGenerator,$toNumber,$toObject,$newArrayWithSpecies, ...), 41 intrinsic constants ($iterationKind*,$generatorField*,$AsyncGeneratorState*, ... , all typedTODOexcept the$MAX_*limits and the unused$Module*phases),$trunc,$newHandledRejectedPromise,$enqueueJob,$Object, five$ERR_*signature overrides whose codes are only thrown from C++ (the generatedErrorCode.d.tsstill declares them), a duplicate$ERR_INVALID_HANDLE_TYPEdeclaration, and the five$ReadableStream*type aliases. The only script that reads this file isgenerate-node-errors.ts, which uses it to decide whether to emit the generic$ERR_*declaration.tsc -p src/jsreports byte-for-byte the same diagnostics before and after (the baseline is not clean, but nothing new appears and nothing disappears).The private-name block further down the file (
$body,$close, ...) has unused entries too, but #38900 is already editing that block, so it is left for after that lands.Verification
bun bd(full debug build; regenerates the class bindings, which is what stops referencing the four removed methods) builds, andcargo check --workspaceis clean: removing a Rust item that still had a caller fails to compile, which is the reference check for everything above.GenericIndexOptional::{get, is_some, is_none}, which an earlier sweep had flagged, turned out to have gained callers since and were kept because of exactly this check.bun run rust:check-all: every CI target triple checks (covers the Windows and macOS watcher backends and thecfg(unix)-onlyposix::read).cargo fmt --all --checkclean.bun bd testontest/js/node/http2/node-http2.test.js(360 pass),h2-conformance,node-http2-continuation,node-http2-invalid-padding,node-http2-streams-rehash(88 pass),test/js/node/watch/fs.watch.test.ts,fs.watchFile.test.ts,test/js/node/quic/quic-endpoint.test.ts,quic-stream.test.ts,test/cli/watch/watch.test.ts,test/js/bun/ffi/ffi.test.js.test/internal/source-lints/(all 25 files) passes, including the newdead-symbols-h2-inbound-watcher-loader-misc.test.ts; all seven of its tests fail againstmain(every one of its 94 checks fires there) and pass here. Because that lint readssrc/js/builtins.d.ts, which was not among the paths that trigger the source-lints workflow, the file is added to.github/workflows/source-lints.ymlso a PR reintroducing a declaration runs the lint itself instead of failing the next unrelated PR.Found but left for a later run
ManifestLoad::LoadFromMemory(src/install): every caller passesLoadFromMemoryFallbackToDisk, so the variant, thecache_behaviorparameter and the memory-only arm inPackageManifestMap::by_name_hash_allow_expiredare dead; touching it means one-line hunks in ninesrc/installfiles that change daily, so it is better done on its own.internalBinding('quic')constants nothing insrc/js/internal/quicdestructures (DEFAULT_MAX_*, the numericCC_ALGO_*, theIDX_STATE_ENDPOINT_*_SIZEentries, ...): unreferenced, but the table deliberately mirrors node's binding surface, so it is noted rather than trimmed.ExternalShared::as_ptr,UTIME_OMITand the unusedbuiltins.d.tsprivate names: either adjacent to hunks in open PRs (Remove dead code from C++ bindings, bindgen glue, ast, and orphaned scripts #37149, Remove dead code from node:http2, the HTTP/2 parser, JSC bindings, uSockets, and the builtin-name tables #38900) or part of a constant pair, so left alone.